package mqtt

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"log"
	"strings"
	"time"

	"iot-cloud-platform/internal/config"
	"iot-cloud-platform/internal/database"
	"iot-cloud-platform/internal/logger"
	"iot-cloud-platform/internal/models"
	"iot-cloud-platform/internal/services/device"

	mqtt "github.com/eclipse/paho.mqtt.golang"
	"github.com/google/uuid"
	"gorm.io/gorm"
)

// MQTTManager handles MQTT broker connections and message processing
type MQTTManager struct {
	client        mqtt.Client
	config        *config.Config
	db            *gorm.DB
	redis         *database.RedisManager
	deviceService *device.DeviceService
	callbacks     map[string]MessageCallback
}

// MessageCallback defines the callback function for message handling
type MessageCallback func(topic string, payload []byte, deviceID uuid.UUID) error

// DeviceMessage represents a message from a device
type DeviceMessage struct {
	DeviceID  uuid.UUID              `json:"device_id"`
	MessageID string                 `json:"message_id"`
	Timestamp time.Time              `json:"timestamp"`
	Type      string                 `json:"type"` // "telemetry", "event", "response"
	Data      map[string]interface{} `json:"data"`
	Location  *models.Location       `json:"location,omitempty"`
}

// CommandMessage represents a command to be sent to a device
type CommandMessage struct {
	CommandID uuid.UUID              `json:"command_id"`
	Type      string                 `json:"type"`
	Method    string                 `json:"method"`
	Params    map[string]interface{} `json:"params"`
	Timestamp time.Time              `json:"timestamp"`
}

// NewMQTTManager creates a new MQTT manager
func NewMQTTManager(cfg *config.Config, db *gorm.DB, redis *database.RedisManager, deviceService *device.DeviceService) (*MQTTManager, error) {
	manager := &MQTTManager{
		config:        cfg,
		db:            db,
		redis:         redis,
		deviceService: deviceService,
		callbacks:     make(map[string]MessageCallback),
	}

	// Set up MQTT client options
	opts := mqtt.NewClientOptions()
	opts.AddBroker(cfg.MQTT.Broker)
	opts.SetClientID(cfg.MQTT.ClientID + "_" + uuid.New().String())
	opts.SetUsername(cfg.MQTT.Username)
	opts.SetPassword(cfg.MQTT.Password)
	opts.SetKeepAlive(60 * time.Second)
	opts.SetPingTimeout(1 * time.Second)
	opts.SetAutoReconnect(true)
	opts.SetMaxReconnectInterval(1 * time.Minute)

	// Set TLS config if using secure connection
	if strings.HasPrefix(cfg.MQTT.Broker, "tls://") || strings.HasPrefix(cfg.MQTT.Broker, "ssl://") {
		tlsConfig := &tls.Config{
			InsecureSkipVerify: false, // Set to true only for testing
		}
		opts.SetTLSConfig(tlsConfig)
	}

	// Set connection handlers
	opts.SetOnConnectHandler(manager.onConnect)
	opts.SetConnectionLostHandler(manager.onConnectionLost)

	// Create MQTT client
	manager.client = mqtt.NewClient(opts)

	return manager, nil
}

// Connect establishes connection to MQTT broker
func (m *MQTTManager) Connect() error {
	token := m.client.Connect()
	if token.Wait() && token.Error() != nil {
		return fmt.Errorf("failed to connect to MQTT broker: %w", token.Error())
	}

	log.Println("Connected to MQTT broker successfully")
	return nil
}

// Disconnect closes MQTT connection
func (m *MQTTManager) Disconnect() {
	m.client.Disconnect(250)
	log.Println("Disconnected from MQTT broker")
}

// onConnect handles successful MQTT connection
func (m *MQTTManager) onConnect(client mqtt.Client) {
	log.Println("MQTT client connected")

	// Subscribe to device topics
	m.subscribeToDeviceTopics()
}

// onConnectionLost handles MQTT connection loss
func (m *MQTTManager) onConnectionLost(client mqtt.Client, err error) {
	logger.GetLogger().WithError(err).Error("MQTT connection lost")
}

// subscribeToDeviceTopics subscribes to all device-related topics
func (m *MQTTManager) subscribeToDeviceTopics() {
	topics := map[string]byte{
		"devices/+/telemetry": 0, // QoS 0 for telemetry data
		"devices/+/events":    1, // QoS 1 for events
		"devices/+/responses": 1, // QoS 1 for command responses
		"devices/+/status":    1, // QoS 1 for device status
		"devices/+/heartbeat": 0, // QoS 0 for heartbeat
	}

	for topic, qos := range topics {
		token := m.client.Subscribe(topic, qos, m.handleMessage)
		if token.Wait() && token.Error() != nil {
			logger.GetLogger().WithError(token.Error()).Errorf("Failed to subscribe to topic: %s", topic)
		} else {
			log.Printf("Subscribed to topic: %s", topic)
		}
	}
}

// handleMessage processes incoming MQTT messages
func (m *MQTTManager) handleMessage(client mqtt.Client, msg mqtt.Message) {
	topic := msg.Topic()
	payload := msg.Payload()

	logger.GetLogger().Infof("Received message on topic: %s", topic)

	// Parse topic to extract device ID and message type
	deviceID, messageType, err := m.parseTopicForDevice(topic)
	if err != nil {
		logger.GetLogger().WithError(err).Error("Failed to parse topic")
		return
	}

	// Verify device exists and is authenticated
	device, err := m.deviceService.GetDevice(nil, deviceID)
	if err != nil {
		logger.GetLogger().WithError(err).Error("Device not found or not authenticated")
		return
	}

	// Update device last seen
	m.deviceService.SetDeviceOnline(nil, deviceID)

	logger.GetLogger().Debugf("Processing message from device %s (%s)", device.DeviceName, deviceID)

	// Process message based on type
	switch messageType {
	case "telemetry":
		m.handleTelemetryMessage(deviceID, payload)
	case "events":
		m.handleEventMessage(deviceID, payload)
	case "responses":
		m.handleResponseMessage(deviceID, payload)
	case "status":
		m.handleStatusMessage(deviceID, payload)
	case "heartbeat":
		m.handleHeartbeatMessage(deviceID, payload)
	default:
		logger.GetLogger().Warnf("Unknown message type: %s", messageType)
	}

	// Execute registered callbacks
	if callback, exists := m.callbacks[messageType]; exists {
		if err := callback(topic, payload, deviceID); err != nil {
			logger.GetLogger().WithError(err).Errorf("Callback error for message type: %s", messageType)
		}
	}
}

// handleTelemetryMessage processes telemetry data from devices
func (m *MQTTManager) handleTelemetryMessage(deviceID uuid.UUID, payload []byte) {
	var message DeviceMessage
	if err := json.Unmarshal(payload, &message); err != nil {
		logger.GetLogger().WithError(err).Error("Failed to unmarshal telemetry message")
		return
	}

	// Set device ID if not present
	if message.DeviceID == uuid.Nil {
		message.DeviceID = deviceID
	}

	// Set timestamp if not present
	if message.Timestamp.IsZero() {
		message.Timestamp = time.Now()
	}

	// Create telemetry data
	telemetryData := models.TelemetryData{
		DeviceID:  deviceID,
		Timestamp: message.Timestamp,
		Data:      message.Data,
		Location:  message.Location,
		Quality:   models.DataQualityGood,
	}

	// Forward to telemetry service (would be implemented later)
	// For now, we'll log the data
	logger.GetLogger().Infof("Received telemetry from device %s: %+v", deviceID, telemetryData)

	// Update device shadow with reported state
	if len(message.Data) > 0 {
		m.deviceService.UpdateDeviceShadow(nil, deviceID, nil, message.Data)
	}
}

// handleEventMessage processes event messages from devices
func (m *MQTTManager) handleEventMessage(deviceID uuid.UUID, payload []byte) {
	var message DeviceMessage
	if err := json.Unmarshal(payload, &message); err != nil {
		logger.GetLogger().WithError(err).Error("Failed to unmarshal event message")
		return
	}

	// Create event log
	eventLog := models.EventLog{
		DeviceID:  &deviceID,
		Type:      models.EventTypeDevice,
		Action:    message.Type,
		Details:   message.Data,
		Timestamp: message.Timestamp,
	}

	if err := m.db.Create(&eventLog).Error; err != nil {
		logger.GetLogger().WithError(err).Error("Failed to save event log")
	}

	logger.GetLogger().Infof("Received event from device %s: %s", deviceID, message.Type)
}

// handleResponseMessage processes command response messages from devices
func (m *MQTTManager) handleResponseMessage(deviceID uuid.UUID, payload []byte) {
	var response struct {
		CommandID uuid.UUID              `json:"command_id"`
		Status    string                 `json:"status"`
		Message   string                 `json:"message"`
		Data      map[string]interface{} `json:"data"`
	}

	if err := json.Unmarshal(payload, &response); err != nil {
		logger.GetLogger().WithError(err).Error("Failed to unmarshal response message")
		return
	}

	// Update command status in database
	updates := map[string]interface{}{
		"status":       models.CommandStatusCompleted,
		"completed_at": time.Now(),
		"response": models.CommandResponse{
			Status:    response.Status,
			Message:   response.Message,
			Data:      response.Data,
			Timestamp: time.Now(),
		},
	}

	if response.Status != "success" {
		updates["status"] = models.CommandStatusFailed
	}

	if err := m.db.Model(&models.Command{}).Where("id = ? AND device_id = ?", response.CommandID, deviceID).Updates(updates).Error; err != nil {
		logger.GetLogger().WithError(err).Error("Failed to update command status")
	}

	logger.GetLogger().Infof("Received command response from device %s for command %s", deviceID, response.CommandID)
}

// handleStatusMessage processes device status messages
func (m *MQTTManager) handleStatusMessage(deviceID uuid.UUID, payload []byte) {
	var status struct {
		Status    string                 `json:"status"`
		Timestamp time.Time              `json:"timestamp"`
		Details   map[string]interface{} `json:"details"`
	}

	if err := json.Unmarshal(payload, &status); err != nil {
		logger.GetLogger().WithError(err).Error("Failed to unmarshal status message")
		return
	}

	// Update device status
	deviceStatus := models.DeviceStatusActive
	if status.Status == "offline" {
		deviceStatus = models.DeviceStatusOffline
	}

	// Set device status based on the received status
	if deviceStatus == models.DeviceStatusOffline {
		m.deviceService.SetDeviceOffline(nil, deviceID)
	} else {
		m.deviceService.SetDeviceOnline(nil, deviceID)
	}
	logger.GetLogger().Infof("Device %s status updated to: %s", deviceID, deviceStatus)
}

// handleHeartbeatMessage processes device heartbeat messages
func (m *MQTTManager) handleHeartbeatMessage(deviceID uuid.UUID, payload []byte) {
	// Simply update last seen timestamp
	m.deviceService.SetDeviceOnline(nil, deviceID)
	logger.GetLogger().Debugf("Received heartbeat from device %s", deviceID)
}

// SendCommand publishes a command to a device
func (m *MQTTManager) SendCommand(deviceID uuid.UUID, command models.Command) error {
	topic := fmt.Sprintf("devices/%s/commands", deviceID.String())

	commandMessage := CommandMessage{
		CommandID: command.ID,
		Type:      "command",
		Method:    command.Payload.Method,
		Params:    command.Payload.Parameters,
		Timestamp: time.Now(),
	}

	payload, err := json.Marshal(commandMessage)
	if err != nil {
		return fmt.Errorf("failed to marshal command: %w", err)
	}

	token := m.client.Publish(topic, 1, false, payload)
	if token.Wait() && token.Error() != nil {
		return fmt.Errorf("failed to publish command: %w", token.Error())
	}

	// Update command status
	m.db.Model(&command).Updates(map[string]interface{}{
		"status":  models.CommandStatusSent,
		"sent_at": time.Now(),
	})

	logger.GetLogger().Infof("Sent command %s to device %s", command.ID, deviceID)
	return nil
}

// SendMessage publishes a generic message to a device
func (m *MQTTManager) SendMessage(deviceID uuid.UUID, messageType string, data interface{}) error {
	topic := fmt.Sprintf("devices/%s/%s", deviceID.String(), messageType)

	payload, err := json.Marshal(data)
	if err != nil {
		return fmt.Errorf("failed to marshal message: %w", err)
	}

	token := m.client.Publish(topic, 1, false, payload)
	if token.Wait() && token.Error() != nil {
		return fmt.Errorf("failed to publish message: %w", token.Error())
	}

	logger.GetLogger().Infof("Sent %s message to device %s", messageType, deviceID)
	return nil
}

// RegisterCallback registers a callback for specific message types
func (m *MQTTManager) RegisterCallback(messageType string, callback MessageCallback) {
	m.callbacks[messageType] = callback
}

// parseTopicForDevice extracts device ID and message type from MQTT topic
func (m *MQTTManager) parseTopicForDevice(topic string) (uuid.UUID, string, error) {
	parts := strings.Split(topic, "/")
	if len(parts) < 3 || parts[0] != "devices" {
		return uuid.Nil, "", fmt.Errorf("invalid topic format: %s", topic)
	}

	deviceIDStr := parts[1]
	deviceID, err := uuid.Parse(deviceIDStr)
	if err != nil {
		return uuid.Nil, "", fmt.Errorf("invalid device ID in topic: %s", deviceIDStr)
	}

	messageType := parts[2]
	return deviceID, messageType, nil
}

// PublishToTopic publishes data to a specific topic
func (m *MQTTManager) PublishToTopic(topic string, qos byte, retain bool, payload interface{}) error {
	data, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("failed to marshal payload: %w", err)
	}

	token := m.client.Publish(topic, qos, retain, data)
	if token.Wait() && token.Error() != nil {
		return fmt.Errorf("failed to publish to topic %s: %w", topic, token.Error())
	}

	return nil
}

// SubscribeToTopic subscribes to a specific topic with callback
func (m *MQTTManager) SubscribeToTopic(topic string, qos byte, callback mqtt.MessageHandler) error {
	token := m.client.Subscribe(topic, qos, callback)
	if token.Wait() && token.Error() != nil {
		return fmt.Errorf("failed to subscribe to topic %s: %w", topic, token.Error())
	}

	log.Printf("Subscribed to topic: %s", topic)
	return nil
}

// IsConnected returns the connection status
func (m *MQTTManager) IsConnected() bool {
	return m.client.IsConnected()
}
